"""数据库连接器模块，负责管理 MySQL 数据库的连接和查询操作。"""

from __future__ import annotations

import logging
from decimal import Decimal
from datetime import datetime
from typing import Any, Dict, List, Optional

try:
    import pymysql
    import pymysql.cursors
except ImportError:
    pymysql = None  # type: ignore[assignment, unused-ignore]

LOGGER = logging.getLogger(__name__)


def json_serial(obj: Any) -> Any:
    """
    JSON序列化辅助函数，处理Decimal和datetime对象。
    
    Args:
        obj: 需要序列化的对象
        
    Returns:
        序列化后的对象
    """
    if isinstance(obj, Decimal):
        return float(obj)  # 将 Decimal 对象转换为 float
    if isinstance(obj, datetime):
        return obj.isoformat()  # 将 datetime 对象转换为 ISO 8601 格式的字符串
    raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")


class MySQLConnector:
    """
    负责管理 MySQL 数据库的连接和查询操作。
    """

    def __init__(self, host: str, port: int, database: str, user: str, password: str) -> None:
        """
        初始化数据库连接器。
        
        Args:
            host: 数据库主机地址
            port: 数据库端口
            database: 数据库名称
            user: 用户名
            password: 密码
        """
        if pymysql is None:
            raise ImportError("pymysql 未安装，请运行: pip install pymysql")
        
        self.host = host
        self.port = port
        self.database = database
        self.user = user
        self.password = password
        self.connection: Optional[pymysql.connections.Connection] = None
        self.connect()

    def connect(self) -> None:
        """尝试建立到 MySQL 数据库的连接。"""
        try:
            self.connection = pymysql.connect(
                host=self.host,
                port=self.port,
                database=self.database,
                user=self.user,
                password=self.password,
                cursorclass=pymysql.cursors.DictCursor,  # 使用 DictCursor 返回字典形式的结果
                charset='utf8mb4',  # 使用 utf8mb4 字符集，支持完整的 Unicode
                connect_timeout=10,  # 连接超时时间（秒）
                read_timeout=30,  # 读取超时时间（秒）
                write_timeout=30,  # 写入超时时间（秒）
                autocommit=True,  # 自动提交，提高性能
            )
            if self.connection.open:  # pymysql 连接对象有一个 open 属性来检查连接状态
                LOGGER.info(f"成功连接到 MySQL 数据库: {self.database}")
            else:
                LOGGER.error("未能连接到 MySQL 数据库。")
        except Exception as e:  # noqa: BLE001
            LOGGER.error(f"连接 MySQL 数据库时发生错误: {e}")
            self.connection = None  # 确保连接失败时设置为 None
            raise

    def execute_query(self, query: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
        """
        执行给定的 SQL 查询，并以字典列表的形式返回结果。
        如果查询没有结果或发生错误，则返回空列表。
        
        Args:
            query: SQL查询语句
            params: 查询参数（可选）
            
        Returns:
            查询结果列表，每个元素是一个字典
        """
        if not self.connection or not self.connection.open:  # 检查连接是否打开
            LOGGER.warning("数据库连接不活跃，尝试重新连接。")
            self.connect()  # 尝试重新连接
            if not self.connection or not self.connection.open:
                LOGGER.error("无法建立数据库连接来执行查询。")
                return []

        cursor = None
        try:
            cursor = self.connection.cursor()  # 已经配置了 DictCursor，所以这里不需要额外参数
            cursor.execute(query, params)
            results = cursor.fetchall()
            return results
        except Exception as e:  # noqa: BLE001
            LOGGER.error(f"执行查询时发生错误: {query} 参数: {params}。错误: {e}")
            return []
        finally:
            if cursor:
                cursor.close()

    def close(self) -> None:
        """关闭数据库连接。"""
        if self.connection and self.connection.open:
            self.connection.close()
            LOGGER.info("MySQL 连接已关闭。")

    def is_connected(self) -> bool:
        """检查数据库连接是否活跃。
        
        Returns:
            如果连接活跃返回 True，否则返回 False
        """
        return self.connection is not None and self.connection.open


class DeviceDataManager:
    """
    管理设备信息的检索、处理和保存。
    它使用 MySQLConnector 来与数据库交互。
    """

    def __init__(self, db_connector: MySQLConnector) -> None:
        """
        初始化设备数据管理器。
        
        Args:
            db_connector: 数据库连接器实例
        """
        self.db_connector = db_connector
        self.database_name = db_connector.database  # 从连接器获取数据库名称

    def get_all_device_info(self) -> List[Dict[str, Any]]:
        """
        从 `iot_device_info` 表中检索所有记录。
        
        Returns:
            设备信息列表
        """
        query = f"SELECT * FROM `{self.database_name}`.iot_device_info"
        return self.db_connector.execute_query(query)

    def get_device_type_node(self, device_type_node_id: int) -> Optional[Dict[str, Any]]:
        """
        根据 `device_type_node_id`（匹配 `sort_number` 字段）
        从 `iot_device_type_node` 表中检索单个记录。
        
        Args:
            device_type_node_id: 设备类型节点ID（sort_number）
            
        Returns:
            设备类型节点数据，如果未找到则返回 None
        """
        query = f"SELECT * FROM `{self.database_name}`.iot_device_type_node WHERE sort_number = %s"
        results = self.db_connector.execute_query(query, (device_type_node_id,))
        return results[0] if results else None  # 返回第一条匹配记录或 None

    def get_object_model_node(self, iot_object_model_id: int) -> List[Dict[str, Any]]:
        """
        根据 `iot_object_model_id`（匹配 `iom_id` 字段）
        从 `iot_object_model_node` 表中检索所有匹配的记录。
        
        Args:
            iot_object_model_id: 物模型ID（iom_id）
            
        Returns:
            物模型节点数据列表，如果没有则为空列表
        """
        query = f"SELECT * FROM `{self.database_name}`.iot_object_model_node WHERE iom_id = %s"
        results = self.db_connector.execute_query(query, (iot_object_model_id,))
        return results  # 返回所有匹配记录的列表，如果没有则为空列表

    def get_device_type_nodes_batch(self, type_node_ids: List[int]) -> Dict[int, Dict[str, Any]]:
        """
        批量获取设备类型节点数据。
        
        Args:
            type_node_ids: 设备类型节点ID列表（sort_number）
            
        Returns:
            以 sort_number 为键的字典，值为对应的设备类型节点数据
        """
        if not type_node_ids:
            return {}
        
        # 使用 IN 子句批量查询
        placeholders = ','.join(['%s'] * len(type_node_ids))
        query = f"SELECT * FROM `{self.database_name}`.iot_device_type_node WHERE sort_number IN ({placeholders})"
        results = self.db_connector.execute_query(query, tuple(type_node_ids))
        
        # 构建字典，以 sort_number 为键
        result_dict = {}
        for record in results:
            sort_number = record.get('sort_number')
            if sort_number is not None:
                result_dict[sort_number] = record
        
        return result_dict

    def get_object_model_nodes_batch(self, iom_ids: List[int]) -> Dict[int, List[Dict[str, Any]]]:
        """
        批量获取物模型节点数据。
        
        Args:
            iom_ids: 物模型ID列表（iom_id）
            
        Returns:
            以 iom_id 为键的字典，值为对应的物模型节点数据列表
        """
        if not iom_ids:
            return {}
        
        # 使用 IN 子句批量查询
        placeholders = ','.join(['%s'] * len(iom_ids))
        query = f"SELECT * FROM `{self.database_name}`.iot_object_model_node WHERE iom_id IN ({placeholders})"
        results = self.db_connector.execute_query(query, tuple(iom_ids))
        
        # 构建字典，以 iom_id 为键，值为列表
        result_dict: Dict[int, List[Dict[str, Any]]] = {}
        for record in results:
            iom_id = record.get('iom_id')
            if iom_id is not None:
                if iom_id not in result_dict:
                    result_dict[iom_id] = []
                result_dict[iom_id].append(record)
        
        return result_dict

    def process_device_data(self) -> List[Dict[str, Any]]:
        """
        处理设备信息，通过 `device_type_node_id` 和 `iot_object_model_id` 字段
        关联 `iot_device_type_node` 和 `iot_object_model_node` 表的数据。
        
        优化策略：使用批量查询替代逐条查询，大幅减少数据库查询次数。
        只有当所有关联都成功时，才将该设备的完整数据包含在最终结果中。
        
        Returns:
            处理后的设备数据列表
        """
        all_device_info = self.get_all_device_info()
        processed_data = []

        if not all_device_info:
            LOGGER.warning("在 iot_device_info 表中未找到任何记录。返回空列表。")
            return []

        LOGGER.info(f"开始处理 {len(all_device_info)} 条设备记录...")
        
        # 第一步：收集所有需要查询的唯一ID
        unique_type_node_ids = set()
        unique_iom_ids = set()
        
        for device_info in all_device_info:
            device_type_node_id = device_info.get('device_type_node_id')
            iot_object_model_id = device_info.get('iot_object_model_id')
            
            if device_type_node_id is not None:
                unique_type_node_ids.add(device_type_node_id)
            if iot_object_model_id is not None:
                unique_iom_ids.add(iot_object_model_id)
        
        LOGGER.info(
            f"收集到 {len(unique_type_node_ids)} 个唯一的设备类型节点ID，"
            f"{len(unique_iom_ids)} 个唯一的物模型ID"
        )
        
        # 第二步：批量查询所有需要的数据
        LOGGER.info("开始批量查询设备类型节点数据...")
        type_node_cache = self.get_device_type_nodes_batch(list(unique_type_node_ids))
        LOGGER.info(f"批量查询完成，获取到 {len(type_node_cache)} 个设备类型节点")
        
        LOGGER.info("开始批量查询物模型节点数据...")
        iom_cache = self.get_object_model_nodes_batch(list(unique_iom_ids))
        LOGGER.info(f"批量查询完成，获取到 {len(iom_cache)} 个物模型的数据")
        
        # 第三步：处理每条设备记录，从缓存中获取关联数据
        processed_count = 0
        skipped_count = 0
        skipped_no_type = 0
        skipped_no_iom = 0

        for device_info in all_device_info:
            device_id = device_info.get('id')
            device_type_node_id = device_info.get('device_type_node_id')
            iot_object_model_id = device_info.get('iot_object_model_id')

            device_type_node_data = None
            object_model_node_data = []  # 初始化为空列表，因为可能返回多个或零个

            # 从缓存中获取 iot_device_type_node 数据
            if device_type_node_id is not None:
                device_type_node_data = type_node_cache.get(device_type_node_id)
            else:
                LOGGER.debug(f"设备 {device_id} 的 device_type_node_id 为 None。跳过此部分关联。")

            # 从缓存中获取 iot_object_model_node 数据
            if iot_object_model_id is not None:
                object_model_node_data = iom_cache.get(iot_object_model_id, [])
            else:
                LOGGER.debug(f"设备 {device_id} 的 iot_object_model_id 为 None。跳过此部分关联。")

            # 核心逻辑：如果任意一个关联失败，则不保存该设备的整个 JSON 数据
            # device_type_node_data 为 None (失败) 或 dict (成功)
            # object_model_node_data 为 [] (失败) 或 [dict, ...] (成功)
            # bool(None) 为 False, bool({}) 为 True
            # bool([]) 为 False, bool([{}]) 为 True
            if device_type_node_data and object_model_node_data:  # 这个条件现在可以正确处理列表为空的情况
                # 复制原始的 device_info 字典，避免修改原始数据
                combined_record = device_info.copy()
                # 根据要求，将匹配到的数据添加到新的键下
                combined_record['iot_device_type_node'] = device_type_node_data
                combined_record['iom_id'] = object_model_node_data  # 现在这里会是一个列表
                processed_data.append(combined_record)
                processed_count += 1
            else:
                skipped_count += 1
                if not device_type_node_data:
                    skipped_no_type += 1
                if not object_model_node_data:
                    skipped_no_iom += 1
                LOGGER.debug(
                    f"跳过设备 {device_id} (device_name: {device_info.get('device_name')}) "
                    f"由于缺少关联数据。 "
                    f"iot_device_type_node 找到: {bool(device_type_node_data)}, "
                    f"iot_object_model_node 找到: {bool(object_model_node_data)}"
                )

        LOGGER.info(
            f"数据处理完成：成功处理 {processed_count} 个设备，"
            f"跳过 {skipped_count} 个设备（其中 {skipped_no_type} 个缺少类型节点，{skipped_no_iom} 个缺少物模型节点）"
        )
        return processed_data

